Aiur

ZelluX 的技术博客

用 Git 管理命令行配置

以前部署新机器时都要把一堆配置文件 scp 过去,今天折腾了下用 Git 统一管理这些配置文件。

做起来很简单,创建一个 dotfiles 目录,把所有要同步的配置文件都放到这个目录下,并重命名去掉文件名开头的点,以免被 Git 忽略。写了一个脚本链接这些配置文件到 HOME 目录:

link-files.rb
1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env ruby

safe_mode = ARGV.include? '--safe'

files = %w(zshrc tmux.conf gitconfig vimrc emacs gitignore_global LS_COLORS)
files.each do |file|
  unless safe_mode and File.exists?("#{ENV['HOME']}/.#{file}")
    %x(ln -s -i -v $PWD/#{file} ~/.#{file})
    puts ".#{file} linked" if safe_mode
  end
end

为了方便同步脚本到 GitHub,我在 .zshrc 中定义了两个命令用来上传、下载最近的脚本配置:

.zshrc
1
2
alias pull-dotfiles='pushd $HOME/dotfiles && git pull origin master && ./link-files.rb --safe; popd'
alias push-dotfiles='pushd $HOME/dotfiles && git add -A && git commit -m "Update" && git push origin master; popd'

pull-dotfiles 可以获取最新的配置,push-dotfiles 则是提交最近的改动到 Git 服务器。我的配置文件在 https://github.com/zellux/dotfiles,希望对大家有用。

利用 ETag 优化 Rails 应用

ETag 是 HTTP 协议的一部分,可以用来检测客户端的缓存是否仍然有效。不少网站都实现了对 ETag 的支持,在 HTTP 响应头中加入当前传送内容的 ETag。以 heroku.com 为例:

1
2
3
4
5
6
7
8
9
$ curl -I www.heroku.com
HTTP/1.1 200 OK
Server: nginx
Date: Fri, 17 Feb 2012 17:36:44 GMT
Content-Type: text/html; charset=utf-8
Connection: keep-alive
Etag: "f74bb78aa48d36e6a0b2072a131b20b9"
Cache-Control: public, max-age=300
Content-Length: 15481

可以看到响应头中给出了请求内容的 ETag 为 f74…b9。接下来我们再次向服务器发起请求,并通过 If-None-Match 字段告之服务器我们已经获得过一份 ETag 为 f74…b9 的内容。服务器收到请求并生成页面后,如果发现页面的 ETag 不变,就会返回 304,声明缓存有效:

1
2
3
4
5
6
7
8
$ curl -I -H 'If-None-Match: "f74bb78aa48d36e6a0b2072a131b20b9"' www.heroku.com
HTTP/1.1 304 Not Modified
Server: nginx
Date: Fri, 17 Feb 2012 17:37:03 GMT
Connection: keep-alive
Last-Modified: Fri, 17 Feb 2012 17:34:00 GMT
Cache-Control: public, max-age=300
ETag: "f74bb78aa48d36e6a0b2072a131b20b9"

浏览器在访问网页时,记录下上一次服务器返回的 ETag,在下一次访问该网页时发送这个 ETag,这样就能有效的利用本地缓存,减少不必要的网络传输了。

Nginx 和 Apache 都很好的支持了 ETag 功能,它们会为静态资源计算 Hash,并将它作为 ETag 返回给客户端。 而对于 CPU 负载较高的应用,还有一个可以优化的空间。Web 服务器在处理客户端请求时,通常的过程是从数据库读取所需数据,交给模版引擎生成 HTML,计算该页面的 ETag,再回复客户端。事实上所需的数据读取完成后,就足以判断缓存是否有效了。

Rails 早在版本 2.2 就加入了 stale? 方法方便开放人员自定义 ETag。以一个博客文章页面为例,页面内容由文章和评论内容生成,如果这两份内容都没有变化,就可以认为客户端缓存依然有效了:

1
2
3
4
5
6
7
8
9
def show
  @article = Article.find(params[:id])
  @comments = @article.comments.all
  if stale?(:etag => [@article, @comments])
    respond_to do |format|
      # ...
    end
  end
end

除此之外,还可以通过 Last Modified 字段控制缓存有效性,Rails 也同样提供了很方便的支持。但是ETag 方法控制缓存更为精确,而且在服务器时间变化时用 Last Modified 容易出错,因此 ETag 往往是更被推荐的方案。

转换 Visio 图片为 EPS 格式

Windows 7 及 Visio 2010 下验证可行:

  • 添加本地打印机,类型选择 Generic -> MS Publisher Color Printer
  • 在 Visio 中打印,选择新添加的打印机,选中 Print to file,点 Properties -> Advanced
  • Document Options -> PostScript Options -> PostScript Output Option 中,选择 Encapsulated PostScript (EPS)
  • 保存时别忘了加后缀名 .eps

这样输出的 EPS 文件的边框是整个 A4 页面,需要用 epstool 把它们周围的白边框去掉:

1
epstool --copy --bbox figure.eps figure-fixed.eps

epstool 在常见的几个软件源中应该都能找到。

一些 ZvT 的心得

一直在国服大师组混,最让我头疼,同时研究的也最多得对战就是 ZvT 了。这里写一点自己关于 ZvT 的心得,欢迎探讨。

前期

  1. 探路农民不能少,我一般在 14d 的时候拉个农民出去探路。探到对方位置后农民别急着走,要在对方路口停留一会儿,注意对手的第一个枪兵有没有跑出来,以便做好防 rush 的准备。同时这也避免了对方农民在你基地旁造一个欺骗性的地堡时,拉过多的农民下来防守。

  2. 分矿处女王造出来后第一件事是铺菌毯而不是补虫卵。第三个女王也一定要出,保证菌毯铺开的速度。

  3. 防火车的话,我现在还是倾向于用蟑螂。用蟑螂的另一个好处在于配合小狗应付对手第一波坦克+枪兵的压制比较有效,第一波出来的时候自爆速度往往还没有好,在没有菌毯的地方很难起效果。

  4. T 开局还有几个变种,如果看到对方出了火车,数量不多,而且其他地面部队也不多的话,还是老老实实补个 bv 造防空吧。

中期

  1. 还是关于侦查。对方二矿开始运作后,小狗要时不时的看看对方家门口。一要注意对手兵力组合,二要注意攻防。如果枪兵不多也没升攻防,对手就有可能用机械化部队了。

  2. 如果对手是标准的枪兵+坦克的组合,我倾向于防下对手第一波部队或者自己飞龙出来后再开三矿。不要因为对手开矿早,就以为自己也能随意补农民发展经济了。现在 T 第一波的 timing 都抓得蛮准,三矿农民补早了很有可能第一波就被推掉了。

  3. 相反,打机械化组合就要利用对方部队成形前的真空期尽早开矿,这也是前面强调小狗侦查的原因。

  4. 开三矿后记得在主矿分矿上都码上几个地堡,这点可以好好像雀茶学学。T 空投你的目的不单单是骚扰经济,而是让自己的主力部队能舒舒服服的推到合适的位置。防下对手一船空投后发现对手已经在咽喉位置架好坦克,摆好枪兵阵形了,这时候就很难打下来。

  5. 对方坦克阵慢慢推进的时候切记一定要耐心,对手总能出现疏漏的。同时飞龙记得吃掉落单的补给部队。

  6. 小狗+自爆和对方枪兵+坦克打正面的时候,能包最好包,不能包也要记得拉一队小狗拦住枪兵,干扰走位。

  7. 怎么打机械化。发现得早的话我一般就做好龙狗换家的准备了,成功率也不低。但是发现晚的话,只能硬着头皮打正面了。现在 GSL 上比较常见是在对手刚出家门,坦克还没架起的时候,用蟑螂吸引火力,同时大量的自爆上去换雷神。我试了几次效果不是很好,可能是时机没选择好的缘故吧。

  8. 飞龙一定要保存好。攒多了威慑力非常大,能很有效的牵制对手的部队。

  9. 天梯上有时还能看到另一个比较奇葩的战术:爆维京。听起来很不靠谱的战术,但是实战中经常能把人打懵(Z 人口补不上,对方维京成型后地面骚扰能力也很强)。当然这种战术应对起来也不难,多预留点人口,每个分矿都码几个堡,准备一两队小狗,接着出飞龙就好了。类似的战术还有火车女妖流,都是能打得你很不舒服,但是一旦放下来就没啥威力的战术。

后期

  1. 我觉得 ZvT 后期的关键在于防空投,因为自爆+狗+感染+母巢王虫的组合基本不怎么怕 T 打正面。但是三攻三防的枪兵拆分矿的能力很强,对应方法,也就只有放好领主侦查,同时在分矿补更多的堡了。

  2. 出母巢王虫后记得拉上所有的女王,加血效果会让对手很无语。

其他补充

一个女王加虫卵的技巧。星际2里有一个切换主基地的快捷键,默认是Backspace,我把它设置成了Tab上边那个键。把所有女王编队后,按住Shift,点切换键,按v后再点一个基地,再按切换键,再v一个基地,这样一轮循环下来可以给所有基地注上虫卵。

不过这么做有一个问题就是女王数量比基地数少的时候,会出现女王到处跑的现象。我现在用的方法是不按Shift,点切换键快速切到基地视角,如果这个基地旁有女王就控制它注卵。这样操作上麻烦了一点,而且注卵时间上有个判断的延迟,但是灵活性高了不少。

转用 octopress 了

jekyll 是一个静态博客生成工具,可配置性很强。但是它的配置对于初学者不是很友好,没有现成的模版,需要自己从头搭一个。octopress 大大简化了这一配置过程,在 jekyll 的基础上提供了一个默认主题,以及一些常用的插件。

Why

在 github 上捣鼓了一阵子 octopress 后,决定把原来的 wordpress 博客的数据转移到这个 octopress 博客上了。相对于 wordpress,octopress 的优点在于:

  • 支持 Markdown 语法。Markdown 是 github、stackoverflow 上的默认标记语言,写笔记我也一直用这个。Mac 平台上有不少好用的 Markdown 编辑器,例如收费的 Byword,免费的 Mou,这些工具都增加了写日志时的愉悦感。

  • 静态。对主页空间没有要求,甚至放到 github pages 上都可以。静态页面如果要加评论,可以考虑 disqus 等第三方 JS 工具。

  • 对内嵌代码支持很好。内置了 pygments ,这里有一份支持语言的列表。值得一提的是 octopress 还支持内嵌 Gist。

  • 日志文件都在本地,而且是纯文本,管理很方便(可以用 git),也不用担心租用的服务器数据丢失等问题。

  • rake new_post; rake gen_deploy 这样写博客很过瘾 :)

How

关于 wordpress 到 octopress 的数据转移,本文结尾的两篇参考文章已经说得很详细了,这里再补充几点:

  • 编码:jekyll 的 wordpressdotcom.rb 用了 yaml 库生成博客文章的 meta 信息,碰到中文标题会出现乱码,换用 ya2yaml 后问题解决。

  • 博客图片:把原来的 wp-content 目录复制过来,再统一改下路径即可。

  • 文章格式:wordpress 导出的文章内容格式比较特殊,不是标准的 HTML,因为它的换行都是有意义的,考虑到这点我就把文章保存成 .markdown 后缀了,效果也不错。Vito 的博客上还介绍了 downmark_it 这个把 HTML 转成 Markdown 的工具。

  • 评论:用了 disqus,它还支持导入 wordpress 上过去的评论。

  • 代码高亮:之前博客用的是一个基于 JS 的 SyntexHighlighter,octopress 自带了 pygments 作为语法高亮工具,两者高亮标记不一样,需要用 nokogiri 转换下。

  • RSS:虽然 octopress 自带了生成 Atom 的插件,但是只能生成一个,而之前博客的 /feed/ 和 /rss.xml 都有人订阅,所以得在 nginx 配置里加了几条重写规则保证这些 URL 都有效。

  • 主题:octopress 支持用 SCSS 自定义主题。现在这个用的主题还是默认的,改天再考虑要不要把原来的主题也移植过来。

  • 写作:建议在 Rakefile 的 new_post 方法结尾启动 Markdown 编辑器打开新生成的文件,这样就免去手动查找的麻烦了。

这个是修改后的 wordpressdotcom.rb,根据我的博客的情况加了一些特殊情况的处理,有同样需求的朋友可以参考下。

参考:

ActiveRecord 的一些细节

对象属性

ActiveRecord 对象在数据库中的属性并不是以实体变量的方式保存的,如果要为一个属性设置默认值的话,

1
2
3
4
5
class Item < ActiveRecord::Base
  def category
    @category || 'n/a'
  end
end

这样的实现是不可行的。读取和修改这些属性时应该使用 read_attribute 和 write_attribute:

1
2
3
4
5
class Item < ActiveRecord::Base
  def category
    read_attribute(:category) || 'n/a'
  end
end

Hash 和相等性

ActiveRecord 的 hash 值是根据主键的值计算出来的,这就意味着未保存对象的 hash 值是不可靠的。同样两个 model 对象的相等比较(即==操作符)也是基于主键的,所以两个 model 对象即使它们的其他属性不一样,仍有可能被当作相等。

查找

find_by_attribute 方法后面加个 ! 号,即使用 find_by_attribute!,就能在找不到对象的时候触发一个 RecordNotFound 异常,而不是返回 nil。

find_or_initialize_by 和 find_or_create_by 也是两个好用的方法,它们在找不到对象时分别使用 new 和 create 新建一个,并用查找的属性初始化新建的对象。

手写 SQL

不得不手写 SQL 同时又要防止注入攻击的一个比较简洁的写法是

1
Order.where("name = :name and pay_type = :pay_type", params[:order])

回调函数

出于性能考虑,after_find 和 after_initialize 只能通过函数声明的方式定义,即不能用类似 before_validation :normalize_fields 这样的形式。

参考

REST 服务的方法

HEAD 方法和 GET 方法比较像,但是它不返回对象的实际表示,只返回一个 HTTP 头。HEAD 可以用来查看对象修改时间、大小等信息,Amazon S3 的客户端就用它来读取文件元信息。

用 PUT 和 POST 创建对象时的一个区别在于,使用前者时客户端知道被创建对象的 URL(例如 /items/3),而后者则不需要客户端了解(例如 /items/new)。

OPTIONS 用来查看客户端对某个资源有那些可用的操作。

正确的设计应当保证:

  • GET 和 HEAD 是安全的,即不会修改任何对象状态。多次调用它们的结果应当和只调用一次甚至不调用一样。
  • GET、HEAD、PUT 和 DELETE 方法是幂等(idempotent)的。多次调用它们的结果应当和只调用一次一样。

这两点保证了在一个不可靠的网络中,客户端仍能进行有效的操作。

参考:Restful Web Services

render 方法的可选参数

:content_type 设置返回内容的 MIME 类型

1
render :file => filename, :content_type => 'application/rss'

:layout 指定 layout

:status 指定返回的 HTTP 代码

:location 指定 HTTP 头中的 Location 字段

Rails 生成的 controller 代码中,create.json 方法在生成对象后会将 Location 设置为新生成对象的 json 地址:

1
2
3
4
5
6
7
8
9
respond_to do |format|
  if @item.save
    format.html { redirect_to @item, notice: 'Item was successfully created.' }
    format.json { render json: @item, status: :created, location: @item }
  else
    format.html { render action: "new" }
    format.json { render json: @item.errors, status: :unprocessable_entity }
  end
end

参考:http://guides.rubyonrails.org/layouts_and_rendering.html#using-render

rspec 跳过指定测试

有些测试比较耗时间,而且很少被修改,如果能在测试的时候跳过它们就能让 spec 快不少。

跳过测试的方法很简单,spec 的 describe 方法可以给对应的测试加上标签,例如

1
2
3
describe SalesController, :slow => true do
    # specs
end

接下来只要在 spec/spec_helper.rb 中声明跳过这个标签即可:

1
2
3
RSpec.configure do |config|
  config.filter_run_excluding :slow => true
end

与 filter_run_excluding 相反的是 filter_run,指定会被运行的标签,不包含在这个列表中的测试将被忽略。

参考:http://www.dixis.com/?p=283

一个简单有效的 hash 算法

最近要给某个类写一个 hash 方法,这个类包括一些整型和字符串属性,需要把它们都放到 hash 中。担心自己想出来的 hash 算法会造成比较严重的冲突,网上搜了一下,发现 Effective Java 中已经介绍过一种简单有效的算法了:

  1. 将任一非零常数赋值给 result
  2. 找到该类中所有需要包含在 hash 中的属性,并根据它们的类型进行计算 c
    1. 对于布尔类型,将它们转换为 0/1
    2. 对于 byte, char, short, 和 int 类型,将它们转换为 int
    3. 对于长整型 long,计算高位和低位的异或结果 (int) (f ^ (f >>> 32))
    4. 对于 float 类型,采用它的二进制表示,在 Java 中为 Float.floatToIntBits,Ruby 中我估计用 Float.hash 也可以
    5. 对于 double 类型,调用 Double.doubleToIntBits 后再次用前面的方法处理得到的 long 类型
    6. 对于数组,利用方法 3 合并计算结果
    7. 对于其它的对象,递归调用该对象的 hash 方法
  3. 将计算结果合并到 result 变量:result = 37 * result + c
  4. 返回 result

另外这个博客的域名已经改成了 blog.yxwang.me,原来的域名 techblog.iamzellux.com 仍然可用,但会重定向到新的域名。